作用域

  一个声明将一个名字引进一个作用域;也就是说,这个名字只能在程序正文的一个特定部分内使用。对于在函数里声明的名字(经常被称为局部名字),其作用域从它声明的那一点开始,直到这个声明所在的块结束为止。一个块就是由{}围起的一段代码。

  一个名字称为是全局的,如果它是在所有函数、类(第10章)和名字空间(8.2节)之外定义的。全局名字的作用域从声明的那一点开始,一直延伸到这个声明所在的文件的结束。在一个块里声明的名字可以屏蔽在其外围的块里所声明的名字或者全局的名字。也就是说,在一个块里可以重新定义一个名字,让它去引用另一个不同的实体。在退出这个块之后,该名字又恢复了它原来的意义。例如,

    int x;            // 全局的x
    void f()
    {
        int x;        // 局部的x屏蔽了全局的x
        x = 1;        // 给局部的x赋值
        {
            int x;    // 屏蔽第一个局部的x
            x = 2;    // 给第二个局部的x赋值
        }
        x = 3;        // 给第一个局部的x赋值
    }
    int* p = &x;      // 取全局x的地址

屏蔽某些名字的情况在写大程序时是不可避免的。但是,读程序的人很容易没有注意某个名字已经被屏蔽了。由于这类错误相对不那么常见,它们反而很难被发现。因此,还是应该尽量避免名字遮蔽的情况。对全局变量或者很大的函数里的局部变量,使用像i或x一类的名字实际上就是自找麻烦。

  被遮蔽的全局名字可以通过作用域解析运算符::去引用。例如,

    int x;
    void f2()
    {
        int x = 1;    // 遮蔽全局的x
        ::x = 2;      // 给全局的x赋值
        x = 2;        // 给局部的x赋值
        // ...
    }

没有办法去使用被遮蔽的局部名字。

  一个名字的作用域从它被声明的那点开始;也就是说,在声明符结束之后,初始式的开始之前。这意味着一个名字甚至可以用于描述自己的初始值。例如,

    int x;
    void f3()
    {
        int x = x;    // 不当:用x自己(未初始化)的值初始化x。
    }

这样做并不非法,只是荒谬。好编译器能对变量在未设置之前就使用提出警告(5.9[9])。

  在一个块里,也有可能同一个名字(不通过::运算符)实际引用的是两个不同的对象。看下面的例子:

    int x = 11;
    void f4()         // 不当:
    {
        int y = x;    // 使用全局的x: y = 11
        int x = 22;
        y = x;        // 使用局部的x: y = 22
    }

函数参数被当做在函数最外层的块中声明的名字。所以

    void f5(int x)
    {
        int x;        // 错误❌
    }

是错误的,因为x在同一个作用域里定义了两次。将这种情况作为错误,将能捕捉到一种相当常见的微妙失误。

🔚